package com.zx.springsecurity.config;

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

/**
 * Spring Security配置类
 * 使用该类替代了spring-security.xml
 * @author 郑雪
 * @date 2021-11-25
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	/**
	 * 自定义的UserDetailsService实现类
	 */
	@Autowired
	UserDetailsService myUserDetailsService;
	
	
	/**
	 * 注入数据源
	 */
	@Autowired
	DataSource dataSource;
	
	/**
	 * 配置对象
	 * 用来实现自动登录（记住我）
	 */
	@Bean
	public PersistentTokenRepository persistentTokenRepository(){
		JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();
		jdbcTokenRepositoryImpl.setDataSource(dataSource);
		//启动时自动创建表
		//jdbcTokenRepositoryImpl.setCreateTableOnStartup(true);
		return jdbcTokenRepositoryImpl;
	};

	/**
	 * 认证
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth)
			throws Exception {
		//1.使用配置类的方式设置登录用户的用户名和密码（内存中加入用户）
		/*
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
		String password = passwordEncoder.encode("123456");
		System.out.println("password : " + password);
		auth.inMemoryAuthentication().withUser("admin").password(password).roles("admin");
		*/
		
		//2.使用配置类的方式设置登录用户的用户名和密码（从数据库中查询）
		//1).设置使用哪个UserDetailsService实现类
		//2).编写实现类，返回User对象，User对象有用户名密码和操作权限
		auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
	}
	
	
	/**
	 * 授权
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		
		//自定义自己编写的登录页面
		http.formLogin()
			.loginPage("/login.jsp")     //指定登录页（如果不指定会访问security自带的登录页）
			.loginProcessingUrl("/user/login")     //指定提交登录表单的地址
			.usernameParameter("username")         //指定登录账号的请求参数名
			.passwordParameter("password")         //指定登录密码的请求参数名
			.defaultSuccessUrl("/test/index")      //指定登录成功去到的页面
			.permitAll()
			
			.and().authorizeRequests()             //对请求进行授权
			.antMatchers("/", "/test/hello", "/user/login").permitAll()           //设置哪些路径可以直接访问，不需要认证
//			.antMatchers("/test/index").hasAuthority("admin,manager")             //设置有 admin 和 manager 权限才能访问这个路径
//			.antMatchers("/test/index").hasAnyAuthority("admin,manager")          //设置有 admin 或 manager 权限就能访问这个路径
//			.antMatchers("/test/index").hasRole("sale")                           //设置有ROLE_sale 角色才能访问这个路径，框架会自动加ROLE_前缀
			.antMatchers("/test/index").hasAnyRole("sale,user")                   //设置有ROLE_sale 或 ROLE_user 角色就能访问这个路径
			.anyRequest().authenticated()         //任意的请求，都需要认证才能访问
			
			.and().logout().logoutUrl("/user/logout").logoutSuccessUrl("/login.jsp") //退出登录
			
			//自动登录（记住我）的实现原理
			//1).认证成功，返回给浏览器一个cookie加密串，数据库中存储这个加密串和用户信息字符串
			//2).再次访问时，获取cookie信息，拿着cookie信息到数据库进行比对，如果查询到对应信息，认证成功，可以登录
			.and().rememberMe().tokenRepository(persistentTokenRepository())      //自动登录（记住我）
			.tokenValiditySeconds(60)  //自动登录的有效时长，单位秒
			.userDetailsService(myUserDetailsService)
			
			//csrf：跨站请求伪造
			//认证成功后，会生成一个csrfToken存储在session或cookie里
			//再次请求（post/put/delete,除了get）会带着这个csrfToken，比对是否一致。
			//所以如果开启csrf防护，提交表单时要加个隐藏域，提交csrfToken的值，否则会提交失败。
			//参考login.jsp
//			.and().csrf().disable()               //关闭csrf防护
			;    
		
		//自定义没有权限访问的错误页
		http.exceptionHandling().accessDeniedPage("/unauth.jsp");
		
	}

	/**
	 * 密码编码器
	 */
	@Bean
	PasswordEncoder passwordEncoder(){
		return new BCryptPasswordEncoder();
	}

}
